package com.wsp.mvcframework.v2.servlet;

import com.wsp.mvcframework.annotation.*;

import javax.naming.ldap.PagedResultsControl;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author: Winston
 * @createTime: 2021/7/9
 */
public class MyDispatcherServlet extends HttpServlet {

    //保存application.properties配置文件中的内容
    private Properties contextConfig = new Properties();
    // 存放所有类名信息的list
    private List<String> classNames = new ArrayList<String>();
    // 模拟IOC容器
    private Map<String, Object> iocMap = new HashMap<String, Object>();
    //保存url和Method的对应关系
//    private Map<String, Method> handlerMapping = new HashMap<String, Method>();
    /**
     * 存放映射关系的List
     */
    private List<Handler> handlerMapping = new ArrayList<Handler>();

    /**
     * 初始化阶段
     *
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        // 1.加载配置文件
        loadProperties(servletConfig.getInitParameter("contextConfigLocation"));
        // 2.扫描所有类
        scanPackage(contextConfig.getProperty("scanPackage"));
        // 3.初始化扫描到的类，并且将它们放入到ICO容器之中
        doInstance();
        // 4.完成依赖注入autowired
        doDi();
        // 5.初始化HandlerMapping也就是将Method和url一一映射
        initHandlerMapping();
        System.out.println("===================My SpringMVC is init=======================");
    }

    /**
     * 将Method和url一一映射
     */
    private void initHandlerMapping() {
        if (iocMap.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            Class<?> instance = entry.getValue().getClass();
            // 获取Controller上的requestMapping
            if (!instance.isAnnotationPresent(MyRequestMapping.class)) {
                continue;
            }
            MyRequestMapping myRequestMapping = instance.getAnnotation(MyRequestMapping.class);
            // 获取类上的url，为了确保路径的正确性，我们先在最前面加上/之后用正则替换
            String classUrl = "/".concat(myRequestMapping.value().trim());
            // 获取所有方法
            Method[] methods = instance.getMethods();
            for (Method method : methods) {
                // 获取方法上requestMapping的url
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                MyRequestMapping methodMyRequestMapping = method.getAnnotation(MyRequestMapping.class);
                // 获取方法上的url，为了确保路径的正确性，我们先在最前面加上/之后用正则替换
                String methodUrl = "/".concat(methodMyRequestMapping.value().trim());
                // 用正则替换掉多余的/
                String url = classUrl.concat(methodUrl).replaceAll("/+", "/");
                // 将方法和url对应
                Handler handler = new Handler(url, method, entry.getValue());
                handlerMapping.add(handler);
            }
        }

    }

    /**
     * 依赖注入
     */
    private void doDi() {
        if (iocMap.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            String beanName = entry.getKey();
            Object instance = entry.getValue();

            Field[] declaredFields = instance.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                // 判断是否加了@MyAutowired注解
                if (!declaredField.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }
                MyAutowired myAutowired = declaredField.getAnnotation(MyAutowired.class);
                // 如果有自定义beanName则取自定义的，如果没有则取首字母小写
                String filedBeanName = myAutowired.value().trim().equals("") ? declaredField.getName() : myAutowired.value().trim();
                // 根据beanName到ioc容器中查找
                Object filedInstance = iocMap.get(filedBeanName);
                // 设置暴力访问
                declaredField.setAccessible(true);
                // 如果存在则给它赋值
                if (filedInstance != null) {
                    try {
                        declaredField.set(instance, filedInstance);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }


    }

    /**
     * 初始化扫描到的类，并且将它们放入到ICO容器之中
     * 我们需要将加了@Controller或者@Service的类放到ioc容器中
     * 这里是进行模拟，因此不考虑如@Component这类的注解
     */
    private void doInstance() {

        //初始化，为DI做准备
        if (classNames.isEmpty()) {
            return;
        }

        for (String className : classNames) {
            // 通过反射获取类信息
            try {
                Class<?> clazz = Class.forName(className);
                // 加了MyController注解的类
                if (clazz.isAnnotationPresent(MyController.class)) {
                    // 获取beanName
                    String beanName = clazz.getSimpleName();
                    // beanName首字母小写
                    beanName = firstToLowerCase(beanName);
                    // 实例化后放到map中
                    iocMap.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    // 加了MyService注解的类
                    MyService myServiceAnnotation = clazz.getAnnotation(MyService.class);
                    // 1.是否起了别名 比如@MyService("service")，如果没起则取首字母小写的beanName
                    String beanName = myServiceAnnotation.value();
                    if ("".equals(beanName.trim())) {
                        beanName = firstToLowerCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    iocMap.put(beanName, instance);

                    // 2.根据类型自动赋值,比如接口上加了@MyService要给接口赋值必须是实现类，这里就采用投机取巧的方式
                    for (Class<?> i : clazz.getInterfaces()) {
                        // 因为一个接口能被多个实现类实现，为了防止这样如果这个接口已经有实现类了，就不用再放到IOC容器中去了
                        if (iocMap.get(i.getName()) != null) {
                            throw new Exception("500 Exception,这个接口" + i.getName() + "的实现类已经存在");
                        }
                        // 将接口的全类名作为key，实例作为value
                        iocMap.put(i.getName(), instance);
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 首字母小写方法，自己使用比较简单
     *
     * @param beanName
     * @return
     */
    private String firstToLowerCase(String beanName) {
        char[] chars = beanName.toCharArray();
        chars[0] += 32;

        return String.valueOf(chars);
    }

    /**
     * 扫描所有类并放到list中区
     *
     * @param scanPackage
     */
    private void scanPackage(String scanPackage) {
        // 根据路径获取所有文件，由于获取到的包路径是.结尾的，我们将.转成/
        URL url = this.getClass().getClassLoader().getResource("/".concat(scanPackage.replace(".", "/")));
        // 获取到的是文件夹路径
        String filePath = url.getFile();
        File file = new File(filePath);
        if (file.exists()) {
            File[] files = file.listFiles();
            // 遍历,扫描所有文件和文件夹
            for (File f : files) {
                // 如果是文件夹则继续递归
                if (f.isDirectory()) {
                    // 将包路径+文件名称传入递归
                    scanPackage(scanPackage + "." + f.getName());
                } else {
                    String fileName = f.getName();
                    // 如果是文件且是.class结尾的文件则将类全路径放到list中去
                    if (fileName.endsWith(".class")) {
                        // 放到List中
                        classNames.add(scanPackage + "." + f.getName().replace(".class", ""));
                    }
                }
            }
        }
    }

    private void loadProperties(String contextConfigLocation) {
        // 读取配置文件
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6、调用，运行阶段
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exection,Detail : " + Arrays.toString(e.getStackTrace()));
        }

    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 获取handler
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 Not Found!!!");
            return;
        }
        // 根据请求查找方法
        Method method = handler.getMethod();
        // 获取方法的形参类型列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 方法的参数
        Object[] params = new Object[parameterTypes.length];

        // 从请求中获取所有参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String value = Arrays.toString(param.getValue());
            // 利用正则去掉数组的[]
            value = value.replaceAll("\\[|\\]","");
            String key = param.getKey();
            if (!handler.getParamIndexMapping().containsKey(key)) {
                continue;
            }
            // 按照顺序保存参数
            Integer index = handler.getParamIndexMapping().get(key);
            params[index] = convert(parameterTypes[index], value);
        }
        // 给HttpServletRequest和HttpServletResponse赋值
        if (handler.getParamIndexMapping().containsKey(HttpServletRequest.class.getName())) {
            Integer index = handler.getParamIndexMapping().get(HttpServletRequest.class.getName());
            params[index] = req;
        }
        if (handler.getParamIndexMapping().containsKey(HttpServletResponse.class.getName())) {
            Integer index = handler.getParamIndexMapping().get(HttpServletResponse.class.getName());
            params[index] = resp;
        }
        // 获取controller
        Object controller = handler.getController();
        // 调用方法
        method.invoke(controller, params);
    }

    private Handler getHandler(HttpServletRequest req) {
        // 获取请求url
        String requestURI = req.getRequestURI();
        StringBuffer requestURL = req.getRequestURL();
        String contextPath = req.getContextPath();
        String servletPath = req.getServletPath();
        String realPath = req.getRealPath("/");

        String url = requestURI.replaceAll("/+", "/");
//        String contextPath = req.getContextPath();
//        url = url.replaceAll(contextPath,"").replaceAll("/+","/");
        // 到handlerMapping中查找是否存在该Url
        for (Handler handler : handlerMapping) {
            if (handler.getUrl().equals(url)) {
                // 如果存在直接返回Handler
                return handler;
            }
        }
        return null;
    }

    //url传过来的参数都是String类型的，HTTP是基于字符串协议
    //只需要把String转换为任意类型就好
    private Object convert(Class<?> type, String value) {
        //如果是int
        if (Integer.class == type) {
            return Integer.valueOf(value);
        } else if (Double.class == type) {
            return Double.valueOf(value);
        }
        //如果还有double或者其他类型，继续加if
        //这时候，我们应该想到策略模式了
        //在这里暂时不实现，希望小伙伴自己来实现
        return value;
    }

    /**
     * 存放映射关系的类
     */
    class Handler {
        /**
         * 请求的url
         */
        private String url;
        /**
         * 方法
         */
        private Method method;
        /**
         * 对应的controller
         */
        private Object controller;

        /**
         * 方法的参数类型列表
         */
        private Class<?>[] parameterTypes;

        /**
         * 参数的名字作为key,参数的顺序，位置作为值
         */
        private Map<String, Integer> paramIndexMapping;

        public Handler(String url, Method method, Object controller) {
            this.url = url;
            this.method = method;
            this.controller = controller;
            this.parameterTypes = method.getParameterTypes();
            paramIndexMapping = new HashMap<String, Integer>();
            putHandlerMappingParam(method);
        }

        /**
         * 将参数和参数列表对应起来
         *
         * @param method
         */
        private void putHandlerMappingParam(Method method) {
            // 获取方法参数上的所有注解
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                for (Annotation annotation : parameterAnnotations[i]) {
                    // 判断是否加了@MyRequestParam
                    if (annotation instanceof MyRequestParam) {
                        // 获取参数名
                        String paramName = ((MyRequestParam) annotation).value();
                        // 将索引和参数名存到Map中
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
            //获取方法中的request和response参数，没有则不获取
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                if (parameterType == HttpServletRequest.class) {
                    // 放到Map中
                    paramIndexMapping.put(parameterType.getName(), i);
                } else if (parameterType == HttpServletResponse.class) {
                    // 放到Map中
                    paramIndexMapping.put(parameterType.getName(), i);
                }
            }
        }

        public String getUrl() {
            return url;
        }

        public Method getMethod() {
            return method;
        }

        public Object getController() {
            return controller;
        }

        public Class<?>[] getParameterTypes() {
            return parameterTypes;
        }

        public Map<String, Integer> getParamIndexMapping() {
            return paramIndexMapping;
        }
    }
}
